package ij.plugin.frame;
import java.awt.BorderLayout;
import java.awt.Button;
import java.awt.Canvas;
import java.awt.Choice;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.Image;
import java.awt.Insets;
import java.awt.Label;
import java.awt.Panel;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Scrollbar;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.AdjustmentEvent;
import java.awt.event.AdjustmentListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.WindowEvent;

import ij.CompositeImage;
import ij.IJ;
import ij.ImageJ;
import ij.ImagePlus;
import ij.ImageStack;
import ij.Prefs;
import ij.Undo;
import ij.WindowManager;
import ij.gui.GUI;
import ij.gui.GenericDialog;
import ij.gui.Roi;
import ij.gui.TrimmedButton;
import ij.gui.YesNoCancelDialog;
import ij.measure.Calibration;
import ij.process.ByteProcessor;
import ij.process.ColorProcessor;
import ij.process.FloatProcessor;
import ij.process.ImageProcessor;
import ij.process.ImageStatistics;
import ij.process.LUT;
import ij.process.StackStatistics;
import tyris.ui.ImageToolsPanel;
import tyris.ui.MainFrame;
import tyris.util.ImageLib;
/**
 * This plugin implements the Brightness/Contrast, Window/level and Color
 * Balance commands, all in the Image/Adjust sub-menu. It allows the user to
 * interactively adjust the brightness and contrast of the active image. It is
 * multi-threaded to provide a more responsive user interface.
 */
public class ContrastAdjuster extends PlugInDialog
        implements Runnable, ActionListener, AdjustmentListener, ItemListener {
    public static final String LOC_KEY = "b&c.loc";
    public static final String[] sixteenBitRanges = { "Automatic", "8-bit (0-255)", "10-bit (0-1023)",
            "12-bit (0-4095)", "14-bit (0-16383)", "15-bit (0-32767)", "16-bit (0-65535)" };
    static final int AUTO_THRESHOLD = 5000;
    static final String[] channelLabels = { "Red", "Green", "Blue", "Cyan", "Magenta", "Yellow", "All" };
    static final String[] altChannelLabels = { "Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5",
            "Channel 6", "All" };
    static final int[] channelConstants = { 4, 2, 1, 3, 5, 6, 7 };
    ContrastPlot plot = new ContrastPlot();
    Thread thread;
    private static ContrastAdjuster instance;
    int minSliderValue = -1, maxSliderValue = -1, brightnessValue = -1, contrastValue = -1;
    int sliderRange = 256;
    boolean doAutoAdjust, doReset, doSet, doApplyLut;
    Panel panel, tPanel;
    Button autoB, resetB, setB, applyB;
    int previousImageID;
    int previousType;
    int previousSlice = 1;
    Object previousSnapshot;
    ImageJ ij;
    double min, max;
    double previousMin, previousMax;
    double defaultMin, defaultMax;
    int contrast, brightness;
    boolean RGBImage;
    Scrollbar minSlider, maxSlider, contrastSlider, brightnessSlider;
    Label minLabel, maxLabel, windowLabel, levelLabel;
    boolean done;
    int autoThreshold;
    GridBagLayout gridbag;
    GridBagConstraints c;
    int y = 0;
    boolean windowLevel, balance;
    Font monoFont = new Font("Monospaced", Font.PLAIN, 12);
    Font sanFont = ImageJ.SansSerif12;
    int channels = 7; // RGB
    Choice choice;
    double windowValue;
    double levelValue;
    public void setAuto() {
        doAutoAdjust = true;
    }
    public void setWL() {
        doSet = true;
    }
    public void setReset() {
        doReset = true;
    }
    public void setWindow(int window) {
        contrastValue = window;
        windowValue = window;
    }
    public void setLevel(int level) {
        brightnessValue = level;
        levelValue = level;
    }
    public ContrastAdjuster() {
        super("B&C");
    }
    
    public void run(String arg) {
        windowLevel = arg.equals("wl");
        balance = arg.equals("balance");
        if (windowLevel)
            setTitle("W&L");
        else if (balance) {
            setTitle("Color");
            channels = 4;
        }
        if (instance != null) {
            if (!instance.getTitle().equals(getTitle())) {
                ContrastAdjuster ca = instance;
                Prefs.saveLocation(LOC_KEY, ca.getLocation());
                ca.close();
            } else {
                instance.toFront();
                return;
            }
        }
        instance = this;
        IJ.register(ContrastAdjuster.class);
        WindowManager.addWindow(this);
        ij = IJ.getInstance();
        gridbag = new GridBagLayout();
        c = new GridBagConstraints();
        setLayout(gridbag);
        // plot
        c.gridx = 0;
        y = 0;
        c.gridy = y++;
        c.fill = GridBagConstraints.BOTH;
        c.anchor = GridBagConstraints.CENTER;
        c.insets = new Insets(10, 10, 0, 10);
        gridbag.setConstraints(plot, c);
        add(plot);
        plot.addKeyListener(ij);
        // min and max labels
        if (!windowLevel) {
            panel = new Panel();
            c.gridy = y++;
            c.insets = new Insets(0, 10, 0, 10);
            gridbag.setConstraints(panel, c);
            panel.setLayout(new BorderLayout());
            minLabel = new Label("      ", Label.LEFT);
            minLabel.setFont(monoFont);
            panel.add("West", minLabel);
            maxLabel = new Label("      ", Label.RIGHT);
            maxLabel.setFont(monoFont);
            panel.add("East", maxLabel);
            add(panel);
        }
        // min slider
        if (!windowLevel) {
            minSlider = new Scrollbar(Scrollbar.HORIZONTAL, sliderRange / 2, 1, 0, sliderRange);
            GUI.fix(minSlider);
            c.gridy = y++;
            c.insets = new Insets(2, 10, 0, 10);
            gridbag.setConstraints(minSlider, c);
            add(minSlider);
            minSlider.addAdjustmentListener(this);
            minSlider.addKeyListener(ij);
            minSlider.setUnitIncrement(1);
            minSlider.setFocusable(false); // prevents blinking on Windows
            addLabel("Minimum", null);
        }
        // max slider
        if (!windowLevel) {
            maxSlider = new Scrollbar(Scrollbar.HORIZONTAL, sliderRange / 2, 1, 0, sliderRange);
            GUI.fix(maxSlider);
            c.gridy = y++;
            c.insets = new Insets(2, 10, 0, 10);
            gridbag.setConstraints(maxSlider, c);
            add(maxSlider);
            maxSlider.addAdjustmentListener(this);
            maxSlider.addKeyListener(ij);
            maxSlider.setUnitIncrement(1);
            maxSlider.setFocusable(false);
            addLabel("Maximum", null);
        }
        // brightness slider
        brightnessSlider = new Scrollbar(Scrollbar.HORIZONTAL, sliderRange / 2, 1, 0, sliderRange);
        GUI.fix(brightnessSlider);
        ImageToolsPanel.setLevelScrollBar(sliderRange / 2, 1, 0, sliderRange-1);
        c.gridy = y++;
        c.insets = new Insets(windowLevel ? 12 : 2, 10, 0, 10);
        gridbag.setConstraints(brightnessSlider, c);
        add(brightnessSlider);
        brightnessSlider.addAdjustmentListener(this);
        brightnessSlider.addKeyListener(ij);
        brightnessSlider.setUnitIncrement(1);
        brightnessSlider.setFocusable(false);
        if (windowLevel)
            addLabel("Level: ", levelLabel = new TrimmedLabel("        "));
        else
            addLabel("Brightness", null);
        // contrast slider
        if (!balance) {
            contrastSlider = new Scrollbar(Scrollbar.HORIZONTAL, sliderRange / 2, 1, 0, sliderRange);
            GUI.fix(contrastSlider);
            ImageToolsPanel.setWindowScrollBar(sliderRange / 2, 1, 0, sliderRange-1);
            c.gridy = y++;
            c.insets = new Insets(2, 10, 0, 10);
            gridbag.setConstraints(contrastSlider, c);
            add(contrastSlider);
            contrastSlider.addAdjustmentListener(this);
            contrastSlider.addKeyListener(ij);
            contrastSlider.setUnitIncrement(1);
            contrastSlider.setFocusable(false);
            if (windowLevel)
                addLabel("Window: ", windowLabel = new TrimmedLabel("        "));
            else
                addLabel("Contrast", null);
        }
        // color channel popup menu
        if (balance) {
            c.gridy = y++;
            c.insets = new Insets(5, 10, 0, 10);
            choice = new Choice();
            addBalanceChoices();
            gridbag.setConstraints(choice, c);
            choice.addItemListener(this);
            // choice.addKeyListener(ij);
            add(choice);
        }
        // buttons
        int trim = IJ.isMacOSX() ? 20 : 0;
        panel = new Panel();
        panel.setLayout(new GridLayout(0, 2, 0, 0));
        autoB = new TrimmedButton("Auto", trim);
        autoB.addActionListener(this);
        autoB.addKeyListener(ij);
        panel.add(autoB);
        resetB = new TrimmedButton("Reset", trim);
        resetB.addActionListener(this);
        resetB.addKeyListener(ij);
        panel.add(resetB);
        setB = new TrimmedButton("Set", trim);
        setB.addActionListener(this);
        setB.addKeyListener(ij);
        panel.add(setB);
        applyB = new TrimmedButton("Apply", trim);
        applyB.addActionListener(this);
        applyB.addKeyListener(ij);
        panel.add(applyB);
        c.gridy = y++;
        c.insets = new Insets(8, 5, 10, 5);
        gridbag.setConstraints(panel, c);
        add(panel);
        addKeyListener(ij); // ImageJ handles keyboard shortcuts
        pack();
        Point loc = Prefs.getLocation(LOC_KEY);
        if (loc != null)
            setLocation(loc);
        else
            GUI.center(this);
        if (IJ.isMacOSX())
            setResizable(false);
        setVisible(false);
//         show();
        thread = new Thread(this, "ContrastAdjuster");
        // thread.setPriority(thread.getPriority()-1);
        thread.start();
//         setup();
    }
    void addBalanceChoices() {
        ImagePlus imp = WindowManager.getCurrentImage();
        if (imp != null && imp.isComposite()) {
            for (int i = 0; i < altChannelLabels.length; i++)
                choice.addItem(altChannelLabels[i]);
        } else {
            for (int i = 0; i < channelLabels.length; i++)
                choice.addItem(channelLabels[i]);
        }
    }
    void addLabel(String text, Label label2) {
        if (label2 == null && IJ.isMacOSX())
            text += "    ";
        panel = new Panel();
        c.gridy = y++;
        int bottomInset = IJ.isMacOSX() ? 4 : 0;
        c.insets = new Insets(0, 10, bottomInset, 0);
        gridbag.setConstraints(panel, c);
        panel.setLayout(new FlowLayout(label2 == null ? FlowLayout.CENTER : FlowLayout.LEFT, 0, 0));
        Label label = new TrimmedLabel(text);
        label.setFont(sanFont);
        panel.add(label);
        if (label2 != null) {
            label2.setFont(monoFont);
            label2.setAlignment(Label.LEFT);
            panel.add(label2);
        }
        add(panel);
    }
    public void setup() {
        ImagePlus imp = MainFrame.getActiveImp();
//        ImagePlus imp = new ImagePlus("D:\\workspaces\\CRScanner-Original\\images\\imgs\\temp\\3333.dcm");
        if (imp != null) {
            setup(imp);
            updatePlot();
            updateLabels(imp);
            imp.updateAndDraw();
        }
    }
    public synchronized void adjustmentValueChanged(AdjustmentEvent e) {
        Object source = e.getSource();
        if (source == minSlider)
            minSliderValue = minSlider.getValue();
        else if (source == maxSlider)
            maxSliderValue = maxSlider.getValue();
        else if (source == contrastSlider)
            contrastValue = contrastSlider.getValue();
        else
            brightnessValue = brightnessSlider.getValue();
        notify();
    }
    public synchronized void actionPerformed(ActionEvent e) {
        Button b = (Button) e.getSource();
        if (b == null)
            return;
        if (b == resetB)
            doReset = true;
        else if (b == autoB)
            doAutoAdjust = true;
        else if (b == setB)
            doSet = true;
        else if (b == applyB)
            doApplyLut = true;
        notify();
    }
    ImageProcessor setup(ImagePlus imp) {
        Roi roi = imp.getRoi();
        if (roi != null)
            roi.endPaste();
        ImageProcessor ip = imp.getProcessor();
        int type = imp.getType();
        int slice = imp.getCurrentSlice();
        RGBImage = type == ImagePlus.COLOR_RGB;
        boolean snapshotChanged = RGBImage && previousSnapshot != null
                && ((ColorProcessor) ip).getSnapshotPixels() != previousSnapshot;
        if (imp.getID() != previousImageID || snapshotChanged || type != previousType || slice != previousSlice)
            setupNewImage(imp, ip);
        previousImageID = imp.getID();
        previousType = type;
        previousSlice = slice;
        return ip;
    }
    void setupNewImage(ImagePlus imp, ImageProcessor ip) {
        // IJ.write("setupNewImage");
        Undo.reset();
        previousMin = min;
        previousMax = max;
        if (RGBImage) {
            ip.snapshot();
            previousSnapshot = ((ColorProcessor) ip).getSnapshotPixels();
        } else
            previousSnapshot = null;
        double min2 = imp.getDisplayRangeMin();
        double max2 = imp.getDisplayRangeMax();
        if (imp.getType() == ImagePlus.COLOR_RGB) {
            min2 = 0.0;
            max2 = 255.0;
        }
        int bitDepth = imp.getBitDepth();
        if (bitDepth == 16 || bitDepth == 32) {
            imp.resetDisplayRange();
            defaultMin = imp.getDisplayRangeMin();
            defaultMax = imp.getDisplayRangeMax();
        } else {
            defaultMin = 0;
            defaultMax = 255;
        }
        setMinAndMax(imp, min2, max2);
        min = imp.getDisplayRangeMin();
        max = imp.getDisplayRangeMax();
        if (IJ.debugMode) {
            IJ.log("min: " + min);
            IJ.log("max: " + max);
            IJ.log("defaultMin: " + defaultMin);
            IJ.log("defaultMax: " + defaultMax);
        }
        plot.defaultMin = defaultMin;
        plot.defaultMax = defaultMax;
        // plot.histogram = null;
        int valueRange = (int) (defaultMax - defaultMin);
        int newSliderRange = valueRange;
        if (newSliderRange > 640 && newSliderRange < 1280)
            newSliderRange /= 2;
        else if (newSliderRange >= 1280)
            newSliderRange /= 5;
        if (newSliderRange < 256)
            newSliderRange = 256;
        if (newSliderRange > 1024)
            newSliderRange = 1024;
        double displayRange = max - min;
        if (valueRange >= 1280 && valueRange != 0 && displayRange / valueRange < 0.25)
            newSliderRange *= 1.6666;
        // IJ.log(valueRange+" "+displayRange+" "+newSliderRange);
        if (newSliderRange != sliderRange) {
            sliderRange = newSliderRange;
            updateScrollBars(null, true);
        } else
            updateScrollBars(null, false);
        if (balance) {
            if (imp.isComposite()) {
                int channel = imp.getChannel();
                if (channel <= 4) {
                    choice.select(channel - 1);
                    channels = channelConstants[channel - 1];
                }
                if (choice.getItem(0).equals("Red")) {
                    choice.removeAll();
                    addBalanceChoices();
                }
            } else { // not composite
                if (choice.getItem(0).equals("Channel 1")) {
                    choice.removeAll();
                    addBalanceChoices();
                }
            }
        }
        if (!doReset)
            plotHistogram(imp);
        autoThreshold = 0;
        if (imp.isComposite())
            IJ.setKeyUp(KeyEvent.VK_SHIFT);
    }
    void setMinAndMax(ImagePlus imp, double min, double max) {
        boolean rgb = imp.getType() == ImagePlus.COLOR_RGB;
        if (channels != 7 && rgb)
            imp.setDisplayRange(min, max, channels);
        else
            imp.setDisplayRange(min, max);
    }
    void updatePlot() {
    	//ContrastPlot plot = new ContrastPlot();
        plot.min = min;
        plot.max = max;
        plot.repaint();
    }
    void updateLabels(ImagePlus imp) {
        double min = imp.getDisplayRangeMin();
        double max = imp.getDisplayRangeMax();
        
        int type = imp.getType();
        Calibration cal = imp.getCalibration();
        boolean realValue = type == ImagePlus.GRAY32;
        if (cal.calibrated()) {
            min = cal.getCValue((int) min);
            max = cal.getCValue((int) max);
            if (type != ImagePlus.GRAY16)
                realValue = true;
        }
        int digits = realValue ? 2 : 0;
        if (windowLevel) {
            // IJ.log(min+" "+max);
            double window = max - min;
            double level = min + (window) / 2.0;
            windowLabel.setText(IJ.d2s(window, digits));
            levelLabel.setText(IJ.d2s(level, digits));
        } else {
            minLabel.setText(IJ.d2s(min, digits));
            maxLabel.setText(IJ.d2s(max, digits));
        }
    }
    void updateScrollBars(Scrollbar sb, boolean newRange) {
        if (sb == null || sb != contrastSlider) {
            double mid = sliderRange / 2;
            double c = ((defaultMax - defaultMin) / (max - min)) * mid;
            if (c > mid)
                c = sliderRange - ((max - min) / (defaultMax - defaultMin)) * mid;
            contrast = (int) c;
            if (contrastSlider != null) {
                if (newRange) {
                    contrastSlider.setValues(contrast, 1, 0, sliderRange);
                    //绐楀绐椾綅宄板��255
                    ImageToolsPanel.setWindowScrollBar(contrast, 1, 0, sliderRange-1);
                } else {
                    ImageToolsPanel.setWindowScrollBar2(contrast);
                    contrastSlider.setValue(contrast);
                }
            }
        }
        if (sb == null || sb != brightnessSlider) {
            double level = min + (max - min) / 2.0;
            double normalizedLevel = 1.0 - (level - defaultMin) / (defaultMax - defaultMin);
            brightness = (int) (normalizedLevel * sliderRange);
            if (newRange) {
                brightnessSlider.setValues(brightness, 1, 0, sliderRange);
                //璁剧疆绐椾綅鐨勪笂闄愬�间负255
                ImageToolsPanel.setLevelScrollBar(brightness, 1, 0, sliderRange-1);
                
            } else {
                brightnessSlider.setValue(brightness);
                ImageToolsPanel.setLevelScrollBar2(brightness);
            }
        }
        if (minSlider != null && (sb == null || sb != minSlider)) {
            if (newRange)
                minSlider.setValues(scaleDown(min), 1, 0, sliderRange);
            else
                minSlider.setValue(scaleDown(min));
        }
        if (maxSlider != null && (sb == null || sb != maxSlider)) {
            if (newRange)
                maxSlider.setValues(scaleDown(max), 1, 0, sliderRange);
            else
                maxSlider.setValue(scaleDown(max));
        }
    }
    int scaleDown(double v) {
        if (v < defaultMin)
            v = defaultMin;
        if (v > defaultMax)
            v = defaultMax;
        return (int) ((v - defaultMin) * (sliderRange - 1.0) / (defaultMax - defaultMin));
    }
    /** Restore image outside non-rectangular roi. */
    void doMasking(ImagePlus imp, ImageProcessor ip) {
        ImageProcessor mask = imp.getMask();
        if (mask != null)
            ip.reset(mask);
    }
    void adjustMin(ImagePlus imp, ImageProcessor ip, double minvalue) {
        min = defaultMin + minvalue * (defaultMax - defaultMin) / (sliderRange - 1.0);
        if (max > defaultMax)
            max = defaultMax;
        if (min > max)
            max = min;
        setMinAndMax(imp, min, max);
        if (min == max)
            setThreshold(ip);
        if (RGBImage)
            doMasking(imp, ip);
        updateScrollBars(minSlider, false);
    }
    void adjustMax(ImagePlus imp, ImageProcessor ip, double maxvalue) {
        max = defaultMin + maxvalue * (defaultMax - defaultMin) / (sliderRange - 1.0);
        // IJ.log("adjustMax: "+maxvalue+" "+max);
        if (min < defaultMin)
            min = defaultMin;
        if (max < min)
            min = max;
        setMinAndMax(imp, min, max);
        if (min == max)
            setThreshold(ip);
        if (RGBImage)
            doMasking(imp, ip);
        updateScrollBars(maxSlider, false);
    }
    void adjustBrightness(ImagePlus imp, ImageProcessor ip, double bvalue) {
        double center = defaultMin + (defaultMax - defaultMin) * ((sliderRange - bvalue) / sliderRange);
        double width = max - min;
        min = center - width / 2.0;
        max = center + width / 2.0;
        setMinAndMax(imp, min, max);
        if (min == max)
            setThreshold(ip);
        if (RGBImage)
            doMasking(imp, ip);
        updateScrollBars(brightnessSlider, false);
    }
    void adjustContrast(ImagePlus imp, ImageProcessor ip, int cvalue) {
        double slope;
        double center = min + (max - min) / 2.0;
        double range = defaultMax - defaultMin;
        double mid = sliderRange / 2;
        if (cvalue <= mid)
            slope = cvalue / mid;
        else
            slope = mid / (sliderRange - cvalue);
        if (slope > 0.0) {
            min = center - (0.5 * range) * slope;
            max = center + (0.5 * range) * slope;
        }
        setMinAndMax(imp, min, max);
        if (RGBImage)
            doMasking(imp, ip);
        updateScrollBars(contrastSlider, false);
    }
    void reset(ImagePlus imp, ImageProcessor ip) {
        if (RGBImage)
            ip.reset();
        int bitDepth = imp.getBitDepth();
        if (bitDepth == 16 || bitDepth == 32) {
            imp.resetDisplayRange();
            defaultMin = imp.getDisplayRangeMin();
            defaultMax = imp.getDisplayRangeMax();
            plot.defaultMin = defaultMin;
            plot.defaultMax = defaultMax;
        }
        min = defaultMin;
        max = defaultMax;
        setMinAndMax(imp, min, max);
        updateScrollBars(null, false);
        plotHistogram(imp);
        autoThreshold = 0;
    }
    void plotHistogram(ImagePlus imp) {
        ImageStatistics stats;
        if (balance && (channels == 4 || channels == 2 || channels == 1) && imp.getType() == ImagePlus.COLOR_RGB) {
            int w = imp.getWidth();
            int h = imp.getHeight();
            byte[] r = new byte[w * h];
            byte[] g = new byte[w * h];
            byte[] b = new byte[w * h];
            ((ColorProcessor) imp.getProcessor()).getRGB(r, g, b);
            byte[] pixels = null;
            if (channels == 4)
                pixels = r;
            else if (channels == 2)
                pixels = g;
            else if (channels == 1)
                pixels = b;
            ImageProcessor ip = new ByteProcessor(w, h, pixels, null);
            stats = ImageStatistics.getStatistics(ip, 0, imp.getCalibration());
        } else {
            int range = imp.getType() == ImagePlus.GRAY16 ? ImagePlus.getDefault16bitRange() : 0;
            if (range != 0 && imp.getProcessor().getMax() == Math.pow(2, range) - 1
                    && !(imp.getCalibration().isSigned16Bit())) {
                ImagePlus imp2 = new ImagePlus("Temp", imp.getProcessor());
                stats = new StackStatistics(imp2, 256, 0, Math.pow(2, range));
            } else
                stats = imp.getStatistics();
        }
        Color color = Color.gray;
        if (imp.isComposite() && !(balance && channels == 7))
            color = ((CompositeImage) imp).getChannelColor();
        plot.setHistogram(stats, color);
    }
    void apply(ImagePlus imp, ImageProcessor ip) {
        if (balance && imp.isComposite())
            return;
        String option = null;
        if (RGBImage)
            imp.unlock();
        if (!imp.lock())
            return;
        if (RGBImage) {
            if (imp.getStackSize() > 1)
                applyRGBStack(imp);
            else {
                ip.snapshot();
                reset(imp, ip);
                imp.changes = true;
                if (Recorder.record) {
                    if (Recorder.scriptMode())
                        Recorder.recordCall("IJ.run(imp, \"Apply LUT\", \"\");");
                    else
                        Recorder.record("run", "Apply LUT");
                }
            }
            imp.unlock();
            return;
        }
        int bitDepth = imp.getBitDepth();
        if (bitDepth == 32) {
            IJ.beep();
            IJ.showStatus("\"Apply\" does not work with 32-bit images");
            imp.unlock();
            return;
        }
        int range = 256;
        if (bitDepth == 16) {
            range = 65536;
            int defaultRange = imp.getDefault16bitRange();
            if (defaultRange > 0)
                range = (int) Math.pow(2, defaultRange) - 1;
        }
        int tableSize = bitDepth == 16 ? 65536 : 256;
        int[] table = new int[tableSize];
        int min = (int) imp.getDisplayRangeMin();
        int max = (int) imp.getDisplayRangeMax();
        if (IJ.debugMode)
            IJ.log("Apply: mapping " + min + "-" + max + " to 0-" + (range - 1));
        for (int i = 0; i < tableSize; i++) {
            if (i <= min)
                table[i] = 0;
            else if (i >= max)
                table[i] = range - 1;
            else
                table[i] = (int) (((double) (i - min) / (max - min)) * range);
        }
        ip.setRoi(imp.getRoi());
        if (imp.getStackSize() > 1 && !imp.isComposite()) {
            ImageStack stack = imp.getStack();
            YesNoCancelDialog d = new YesNoCancelDialog(new Frame(), "Entire Stack?",
                    "Apply LUT to all " + stack.getSize() + " slices in the stack?");
            if (d.cancelPressed()) {
                imp.unlock();
                return;
            }
            if (d.yesPressed()) {
                if (imp.getStack().isVirtual()) {
                    imp.unlock();
                    IJ.error(
                            "\"Apply\" does not work with virtual stacks. Use\nImage>Duplicate to convert to a normal stack.");
                    return;
                }
                int current = imp.getCurrentSlice();
                ImageProcessor mask = imp.getMask();
                for (int i = 1; i <= imp.getStackSize(); i++) {
                    imp.setSlice(i);
                    ip = imp.getProcessor();
                    if (mask != null)
                        ip.snapshot();
                    ip.applyTable(table);
                    ip.reset(mask);
                }
                imp.setSlice(current);
                option = "stack";
            } else {
                if (ip.getMask() != null)
                    ip.snapshot();
                ip.applyTable(table);
                ip.reset(ip.getMask());
                option = "slice";
            }
        } else {
            if (ip.getMask() != null)
                ip.snapshot();
            ip.applyTable(table);
            ip.reset(ip.getMask());
        }
        reset(imp, ip);
        imp.changes = true;
        imp.unlock();
        if (Recorder.record) {
            if (Recorder.scriptMode()) {
                if (option == null)
                    option = "";
                Recorder.recordCall("IJ.run(imp, \"Apply LUT\", \"" + option + "\");");
            } else {
                if (option != null)
                    Recorder.record("run", "Apply LUT", option);
                else
                    Recorder.record("run", "Apply LUT");
            }
        }
    }
    void applyRGBStack(ImagePlus imp) {
        double min = imp.getDisplayRangeMin();
        double max = imp.getDisplayRangeMax();
        if (IJ.debugMode)
            IJ.log("applyRGBStack: " + min + "-" + max);
        int current = imp.getCurrentSlice();
        int n = imp.getStackSize();
        if (!IJ.showMessageWithCancel("Update Entire Stack?", "Apply brightness and contrast settings\n" + "to all " + n
                + " slices in the stack?\n \n" + "NOTE: There is no Undo for this operation."))
            return;
        ImageProcessor mask = imp.getMask();
        Rectangle roi = imp.getRoi() != null ? imp.getRoi().getBounds() : null;
        ImageStack stack = imp.getStack();
        for (int i = 1; i <= n; i++) {
            IJ.showProgress(i, n);
            IJ.showStatus(i + "/" + n);
            if (i != current) {
                ImageProcessor ip = stack.getProcessor(i);
                ip.setRoi(roi);
                if (mask != null)
                    ip.snapshot();
                if (channels != 7)
                    ((ColorProcessor) ip).setMinAndMax(min, max, channels);
                else
                    ip.setMinAndMax(min, max);
                if (mask != null)
                    ip.reset(mask);
            }
        }
        imp.setStack(null, stack);
        imp.setSlice(current);
        imp.changes = true;
        previousImageID = 0;
        setup();
        if (Recorder.record) {
            if (Recorder.scriptMode())
                Recorder.recordCall("IJ.run(imp, \"Apply LUT\", \"stack\");");
            else
                Recorder.record("run", "Apply LUT", "stack");
        }
    }
    void setThreshold(ImageProcessor ip) {
        if (!(ip instanceof ByteProcessor))
            return;
        if (((ByteProcessor) ip).isInvertedLut())
            ip.setThreshold(max, 255, ImageProcessor.NO_LUT_UPDATE);
        else
            ip.setThreshold(0, max, ImageProcessor.NO_LUT_UPDATE);
    }
    void autoAdjust(ImagePlus imp, ImageProcessor ip) {
        if (RGBImage)
            ip.reset();
        Calibration cal = imp.getCalibration();
        imp.setCalibration(null);
        ImageStatistics stats = imp.getStatistics(); // get uncalibrated stats
        imp.setCalibration(cal);
        int limit = stats.pixelCount / 10;
        int[] histogram = stats.histogram;
        if (autoThreshold < 10)
            autoThreshold = AUTO_THRESHOLD;
        else
            autoThreshold /= 2;
        int threshold = stats.pixelCount / autoThreshold;
        int i = -1;
        boolean found = false;
        int count;
        do {
            i++;
            count = histogram[i];
            if (count > limit)
                count = 0;
            found = count > threshold;
        } while (!found && i < 255);
        int hmin = i;
        i = 256;
        do {
            i--;
            count = histogram[i];
            if (count > limit)
                count = 0;
            found = count > threshold;
        } while (!found && i > 0);
        int hmax = i;
        Roi roi = imp.getRoi();
        if (hmax >= hmin) {
            if (RGBImage)
                imp.deleteRoi();
            min = stats.histMin + hmin * stats.binSize;
            max = stats.histMin + hmax * stats.binSize;
            if (min == max) {
                min = stats.min;
                max = stats.max;
            }
            setMinAndMax(imp, min, max);
            if (RGBImage && roi != null)
                imp.setRoi(roi);
        } else {
            reset(imp, ip);
            return;
        }
        updateScrollBars(null, false);
        // if (roi!=null) { ???
        // ImageProcessor mask = roi.getMask();
        // if (mask!=null)
        // ip.reset(mask);
        // }
        if (Recorder.record) {
            if (Recorder.scriptMode())
                Recorder.recordCall("IJ.run(imp, \"Enhance Contrast\", \"saturated=0.35\");");
            else
                Recorder.record("run", "Enhance Contrast", "saturated=0.35");
        }
    }
    void setMinAndMax(ImagePlus imp, ImageProcessor ip) {
        min = imp.getDisplayRangeMin();
        max = imp.getDisplayRangeMax();
        Calibration cal = imp.getCalibration();
        int digits = (ip instanceof FloatProcessor) || cal.calibrated() ? 2 : 0;
        double minValue = cal.getCValue(min);
        double maxValue = cal.getCValue(max);
        int channels = imp.getNChannels();
        GenericDialog gd = new GenericDialog("Set Display Range");
        gd.addNumericField("Minimum displayed value: ", minValue, digits);
        gd.addNumericField("Maximum displayed value: ", maxValue, digits);
        gd.addChoice("Unsigned 16-bit range:", sixteenBitRanges, sixteenBitRanges[get16bitRangeIndex()]);
        String label = "Propagate to all other ";
        label = imp.isComposite() ? label + channels + " channel images" : label + "open images";
        gd.addCheckbox(label, false);
        boolean allChannels = false;
        if (imp.isComposite() && channels > 1) {
            label = "Propagate to the other ";
            label = channels == 2 ? label + "channel of this image"
                    : label + (channels - 1) + " channels of this image";
            gd.addCheckbox(label, allChannels);
        }
        gd.showDialog();
        if (gd.wasCanceled())
            return;
        minValue = gd.getNextNumber();
        maxValue = gd.getNextNumber();
        minValue = cal.getRawValue(minValue);
        maxValue = cal.getRawValue(maxValue);
        int rangeIndex = gd.getNextChoiceIndex();
        int range1 = ImagePlus.getDefault16bitRange();
        int range2 = set16bitRange(rangeIndex);
        if (range1 != range2 && imp.getType() == ImagePlus.GRAY16 && !cal.isSigned16Bit()) {
            reset(imp, ip);
            minValue = imp.getDisplayRangeMin();
            maxValue = imp.getDisplayRangeMax();
        }
        boolean propagate = gd.getNextBoolean();
        if (imp.isComposite() && channels > 1)
            allChannels = gd.getNextBoolean();
        if (maxValue >= minValue) {
            min = minValue;
            max = maxValue;
            setMinAndMax(imp, min, max);
            updateScrollBars(null, false);
            if (RGBImage)
                doMasking(imp, ip);
            if (allChannels) {
                int channel = imp.getChannel();
                for (int c = 1; c <= channels; c++) {
                    imp.setPositionWithoutUpdate(c, imp.getSlice(), imp.getFrame());
                    imp.setDisplayRange(min, max);
                    // IJ.log("setDisplayRange: "+c+" "+min+" "+max);
                }
                ((CompositeImage) imp).reset();
                imp.setPosition(channel, imp.getSlice(), imp.getFrame());
            }
            if (propagate)
                propagate(imp);
            if (Recorder.record) {
                if (imp.getBitDepth() == 32)
                    recordSetMinAndMax(min, max);
                else {
                    int imin = (int) min;
                    int imax = (int) max;
                    if (cal.isSigned16Bit()) {
                        imin = (int) cal.getCValue(imin);
                        imax = (int) cal.getCValue(imax);
                    }
                    recordSetMinAndMax(imin, imax);
                }
                if (range2 > 0) {
                    if (Recorder.scriptMode())
                        Recorder.recordCall("ImagePlus.setDefault16bitRange(" + range2 + ");");
                    else
                        Recorder.recordString("call(\"ij.ImagePlus.setDefault16bitRange\", " + range2 + ");\n");
                }
            }
        }
    }
    private void propagate(ImagePlus img) {
        int[] list = WindowManager.getIDList();
        if (list == null)
            return;
        int nImages = list.length;
        if (nImages <= 1)
            return;
        ImageProcessor ip = img.getProcessor();
        double min = ip.getMin();
        double max = ip.getMax();
        int depth = img.getBitDepth();
        if (depth == 24)
            return;
        int id = img.getID();
        if (img.isComposite()) {
            int nChannels = img.getNChannels();
            for (int i = 0; i < nImages; i++) {
                ImagePlus img2 = WindowManager.getImage(list[i]);
                if (img2 == null)
                    continue;
                int nChannels2 = img2.getNChannels();
                if (img2.isComposite() && img2.getBitDepth() == depth && img2.getID() != id
                        && img2.getNChannels() == nChannels && img2.getWindow() != null) {
                    int channel = img2.getChannel();
                    for (int c = 1; c <= nChannels; c++) {
                        LUT lut = ((CompositeImage) img).getChannelLut(c);
                        img2.setPosition(c, img2.getSlice(), img2.getFrame());
                        img2.setDisplayRange(lut.min, lut.max);
                        img2.updateAndDraw();
                    }
                    img2.setPosition(channel, img2.getSlice(), img2.getFrame());
                }
            }
        } else {
            for (int i = 0; i < nImages; i++) {
                ImagePlus img2 = WindowManager.getImage(list[i]);
                if (img2 != null && img2.getBitDepth() == depth && img2.getID() != id && img2.getNChannels() == 1
                        && img2.getWindow() != null) {
                    ImageProcessor ip2 = img2.getProcessor();
                    ip2.setMinAndMax(min, max);
                    img2.updateAndDraw();
                }
            }
        }
    }
    public static int get16bitRangeIndex() {
        int range = ImagePlus.getDefault16bitRange();
        int index = 0;
        if (range == 8)
            index = 1;
        else if (range == 10)
            index = 2;
        else if (range == 12)
            index = 3;
        else if (range == 14)
            index = 4;
        else if (range == 15)
            index = 5;
        else if (range == 16)
            index = 6;
        return index;
    }
    public static int set16bitRange(int index) {
        int range = 0;
        if (index == 1)
            range = 8;
        else if (index == 2)
            range = 10;
        else if (index == 3)
            range = 12;
        else if (index == 4)
            range = 14;
        else if (index == 5)
            range = 15;
        else if (index == 6)
            range = 16;
        ImagePlus.setDefault16bitRange(range);
        return range;
    }
    public static String[] getSixteenBitRanges() {
        return sixteenBitRanges;
    }
    void setWindowLevel(ImagePlus imp, ImageProcessor ip) {
        min = imp.getDisplayRangeMin();
        max = imp.getDisplayRangeMax();
        Calibration cal = imp.getCalibration();
        int digits = (ip instanceof FloatProcessor) || cal.calibrated() ? 2 : 0;
        double minValue = cal.getCValue(min);
        double maxValue = cal.getCValue(max);
        // IJ.log("setWindowLevel: "+min+" "+max);
        double windowValue = maxValue - minValue;
        double levelValue = minValue + windowValue / 2.0;
        GenericDialog gd = new GenericDialog("Set W&L");
        gd.addNumericField("Window Center (Level): ", levelValue, digits);
        gd.addNumericField("Window Width: ", windowValue, digits);
        gd.addCheckbox("Propagate to all open images", false);
        gd.showDialog();
        if (gd.wasCanceled())
            return;
        levelValue = gd.getNextNumber();
        windowValue = gd.getNextNumber();
        minValue = levelValue - (windowValue / 2.0);
        maxValue = levelValue + (windowValue / 2.0);
        minValue = cal.getRawValue(minValue);
        maxValue = cal.getRawValue(maxValue);
        boolean propagate = gd.getNextBoolean();
        if (maxValue >= minValue) {
            min = minValue;
            max = maxValue;
            setMinAndMax(imp, minValue, maxValue);
            updateScrollBars(null, false);
            if (RGBImage)
                doMasking(imp, ip);
            if (propagate)
                IJ.runMacroFile("ij.jar:PropagateMinAndMax");
            if (Recorder.record) {
                if (imp.getBitDepth() == 32)
                    recordSetMinAndMax(min, max);
                else {
                    int imin = (int) min;
                    int imax = (int) max;
                    if (cal.isSigned16Bit()) {
                        imin = (int) cal.getCValue(imin);
                        imax = (int) cal.getCValue(imax);
                    }
                    recordSetMinAndMax(imin, imax);
                }
            }
        }
    }
    void recordSetMinAndMax(double min, double max) {
        if ((int) min == min && (int) max == max) {
            int imin = (int) min, imax = (int) max;
            if (Recorder.scriptMode())
                Recorder.recordCall("IJ.setMinAndMax(imp, " + imin + ", " + imax + ");");
            else
                Recorder.record("setMinAndMax", imin, imax);
        } else {
            if (Recorder.scriptMode())
                Recorder.recordCall("IJ.setMinAndMax(imp, " + min + ", " + max + ");");
            else
                Recorder.record("setMinAndMax", min, max);
        }
    }
    static final int RESET = 0, AUTO = 1, SET = 2, APPLY = 3, THRESHOLD = 4, MIN = 5, MAX = 6, BRIGHTNESS = 7,
            CONTRAST = 8, UPDATE = 9;
    // Separate thread that does the potentially time-consuming processing
    public void run() {
        while (!done) {
            synchronized (this) {
                try {
                    wait();
                } catch (InterruptedException e) {
                }
            }
            doUpdate();
        }
    }
    public void doUpdate() {
    	//鑾峰彇褰卞儚锛岃祴鍊兼瀬澶ф瀬灏忓�煎湪setup鏂规硶鍐呰幏鍙栧奖鍍�
    	setup();
        ImagePlus imp;
        ImageProcessor ip;
        int action;
        int minvalue = minSliderValue;
        int maxvalue = maxSliderValue;
        int bvalue = brightnessValue;
        int cvalue = contrastValue;
        if (doReset)
            action = RESET;
        else if (doAutoAdjust)
            action = AUTO;
        else if (doSet)
            action = SET;
        else if (doApplyLut)
            action = APPLY;
        else if (minSliderValue >= 0)
            action = MIN;
        else if (maxSliderValue >= 0)
            action = MAX;
        else if (brightnessValue >= 0)
            action = BRIGHTNESS;
        else if (contrastValue >= 0)
            action = CONTRAST;
        else
            return;
        minSliderValue = maxSliderValue = brightnessValue = contrastValue = -1;
        doReset = doAutoAdjust = doSet = doApplyLut = false;
        // imp = CRScanner.mainFrame.getCurrentImp();
        imp = MainFrame.getActiveImp();
        if (imp == null) {
            IJ.beep();
            IJ.showStatus("No image");
            return;
        } else if (imp.getOverlay() != null && imp.getOverlay().isCalibrationBar()) {
            IJ.beep();
            IJ.showStatus("Has calibration bar");
            return;
        }
        ip = imp.getProcessor();
        if (RGBImage && !imp.lock()) {
            imp = null;
            return;
        }
        // IJ.write("setup: "+(imp==null?"null":imp.getTitle()));
        switch (action) {
        case RESET:
            reset(imp, ip);
            if (Recorder.record) {
                if (Recorder.scriptMode())
                    Recorder.recordCall("IJ.resetMinAndMax(imp);");
                else
                    Recorder.record("resetMinAndMax");
            }
            break;
        case AUTO:
            autoAdjust(imp, ip);
            break;
        case SET:
            if (windowLevel)
                setWindowLevel(imp, ip);
            else
                setMinAndMax(imp, ip);
            break;
        case APPLY:
            apply(imp, ip);
            break;
        case MIN:
            adjustMin(imp, ip, minvalue);
            break;
        case MAX:
            adjustMax(imp, ip, maxvalue);
            break;
        case BRIGHTNESS:
            adjustBrightness(imp, ip, bvalue);
            break;
        case CONTRAST:
            adjustContrast(imp, ip, cvalue);
            break;
        }
        updatePlot();
        updateLabels(imp);
        if ((IJ.shiftKeyDown() || (balance && channels == 7)) && imp.isComposite())
            ((CompositeImage) imp).updateAllChannelsAndDraw();
        else
            imp.updateChannelAndDraw();
        if (RGBImage)
            imp.unlock();
//        HistogramWindow hw = new HistogramWindow(MainFrame.getActiveImp());
    }
    /** Overrides close() in PlugInDialog. */
    public void close() {
        super.close();
        instance = null;
        done = true;
        Prefs.saveLocation(LOC_KEY, getLocation());
        synchronized (this) {
            notify();
        }
    }
    public void windowActivated(WindowEvent e) {
        super.windowActivated(e);
        if (IJ.isMacro()) {
            // do nothing if macro and RGB image
            ImagePlus imp2 = MainFrame.getActiveImp();
            if (imp2 != null && imp2.getBitDepth() == 24) {
                return;
            }
        }
        previousImageID = 0; // user may have modified image
        setup();
        WindowManager.setWindow(this);
    }
    public synchronized void itemStateChanged(ItemEvent e) {
        int index = choice.getSelectedIndex();
        channels = channelConstants[index];
        ImagePlus imp = WindowManager.getCurrentImage();
        if (imp != null && imp.isComposite()) {
            if (index + 1 <= imp.getNChannels())
                imp.setPosition(index + 1, imp.getSlice(), imp.getFrame());
            else {
                choice.select(channelLabels.length - 1);
                channels = 7;
            }
        } else
            doReset = true;
        notify();
    }
    /** Resets this ContrastAdjuster and brings it to the front. */
    public void updateAndDraw() {
        previousImageID = 0;
        toFront();
    }
    /** Updates the ContrastAdjuster. */
    public static void update() {
        if (instance != null) {
            instance.previousImageID = 0;
            instance.setup();
        }
    }
    public int getSliderRange() {
        return sliderRange;
    }
} // ContrastAdjuster class
class ContrastPlot extends Canvas implements MouseListener {
    static final int WIDTH = 128, HEIGHT = 64;
    double defaultMin = 0;
    double defaultMax = 255;
    double min = 0;
    double max = 255;
    int[] histogram;
    int hmax;
    Image os;
    Graphics osg;
    Color color = Color.gray;
    public ContrastPlot() {
        addMouseListener(this);
        setSize(WIDTH + 1, HEIGHT + 1);
    }
    /**
     * Overrides Component getPreferredSize(). Added to work around a bug in
     * Java 1.4.1 on Mac OS X.
     */
    public Dimension getPreferredSize() {
        return new Dimension(WIDTH + 1, HEIGHT + 1);
    }
    void setHistogram(ImageStatistics stats, Color color) {
        this.color = color;
        histogram = stats.histogram;
        if (histogram.length != 256) {
            histogram = null;
            return;
        }
        double scale = WIDTH / 256.0;
        for (int i = 0; i < WIDTH; i++) {
            int index = (int) (i / scale);
            histogram[i] = (histogram[index] + histogram[index + 1]) / 2;
        }
        int maxCount = 0;
        int mode = 0;
        for (int i = 0; i < WIDTH; i++) {
            if (histogram[i] > maxCount) {
                maxCount = histogram[i];
                mode = i;
            }
        }
        int maxCount2 = 0;
        for (int i = 0; i < WIDTH; i++) {
            if ((histogram[i] > maxCount2) && (i != mode))
                maxCount2 = histogram[i];
        }
        hmax = stats.maxCount;
        if ((hmax > (maxCount2 * 2)) && (maxCount2 != 0)) {
            hmax = (int) (maxCount2 * 1.5);
            histogram[mode] = hmax;
        }
        os = null;
    }
    public void update(Graphics g) {
        paint(g);
    }
    public void paint(Graphics g) {
        int x1, y1, x2, y2;
        double scale = (double) WIDTH / (defaultMax - defaultMin);
        double slope = 0.0;
        if (max != min)
            slope = HEIGHT / (max - min);
        if (min >= defaultMin) {
            x1 = (int) (scale * (min - defaultMin));
            y1 = HEIGHT;
        } else {
            x1 = 0;
            if (max > min)
                y1 = HEIGHT - (int) ((defaultMin - min) * slope);
            else
                y1 = HEIGHT;
        }
        if (max <= defaultMax) {
            x2 = (int) (scale * (max - defaultMin));
            y2 = 0;
        } else {
            x2 = WIDTH;
            if (max > min)
                y2 = HEIGHT - (int) ((defaultMax - min) * slope);
            else
                y2 = 0;
        }
        if (histogram != null) {
            if (os == null && hmax != 0) {
                os = createImage(WIDTH, HEIGHT);
                osg = os.getGraphics();
                osg.setColor(Color.white);
                osg.fillRect(0, 0, WIDTH, HEIGHT);
                osg.setColor(color);
                for (int i = 0; i < WIDTH; i++)
                    osg.drawLine(i, HEIGHT, i, HEIGHT - ((int) (HEIGHT * histogram[i]) / hmax));
                osg.dispose();
            }
            if (os != null)
                g.drawImage(os, 0, 0, this);
        } else {
            g.setColor(Color.white);
            g.fillRect(0, 0, WIDTH, HEIGHT);
        }
        g.setColor(Color.black);
        g.drawLine(x1, y1, x2, y2);
        g.drawLine(x2, HEIGHT - 5, x2, HEIGHT);
        g.drawRect(0, 0, WIDTH, HEIGHT);
    }
    public void mousePressed(MouseEvent e) {
    }
    public void mouseReleased(MouseEvent e) {
    }
    public void mouseExited(MouseEvent e) {
    }
    public void mouseClicked(MouseEvent e) {
    }
    public void mouseEntered(MouseEvent e) {
    }
} // ContrastPlot class
class TrimmedLabel extends Label {
    int trim = IJ.isMacOSX() ? 0 : 6;
    public TrimmedLabel(String title) {
        super(title);
    }
    public Dimension getMinimumSize() {
        return new Dimension(super.getMinimumSize().width, super.getMinimumSize().height - trim);
    }
    public Dimension getPreferredSize() {
        return getMinimumSize();
    }
} // TrimmedLabel class